﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Xml.Linq;
using Red.Controls;
using Red.Controls.Shell;
using Red.Controls.Utils;
using Red.Core;
using Red.Core.Extensions;
using Red.Launcher.Dialogs;
using Red.Launcher.Executor;
using Red.Launcher.Utilities;
using Metro = MahApps.Metro.Controls;
using P4 = Red.Core.Perforce;
using Packages = Red.Launcher.ViewModels.Packages;
using Workspaces = Red.Launcher.ViewModels.Workspaces;

namespace Red.Launcher
{
    namespace LauncherWindowSettings
    {
        public class EnvironmentVariables
        {
            public bool Create { get; set; }
            public bool Persist { get; set; }
        }

        public class Flyouts
        {
            public FlyoutMode Direction { get; set; } = FlyoutMode.Outwards;
            public double RecentFilesHeight { get; set; } = 200;
        }

        public class Settings
        {
            public EnvironmentVariables EnvironmentVariables { get; set; } = new EnvironmentVariables();
            public Flyouts Flyouts { get; set; } = new Flyouts();
            public bool ShowDocumentationHelpAtStartup { get; set; } = true;
        }
    }

    namespace LauncherWindowData
    {
        public class EnvironmentVariables
        {
            public string Path { get; set; }
        }

        public class RunConfig
        {
            public Run.Config Editor { get; set; }
            public Run.Config Game { get; set; }
            public Run.Config ScriptEditor { get; set; }
        }

        public class AppConfig
        {
            public double TileMinVersion { get; set; }
            public RunConfig RunConfig { get; set; }
            public EnvironmentVariables EnvironmentVariables { get; set; }
        }
    }

    public partial class LauncherWindow : RedMainWindow, INotifyPropertyChanged, IAnimationWrapper, IEnvironmentalVariableSource
    {
        #region Private Types

        private class CloseSettingsFlyout : ICommand
        {
            SettingsView _view;
            Metro.Flyout _flyout;

            public CloseSettingsFlyout( SettingsView view, Metro.Flyout flyout )
            {
                _view = view;
                _flyout = flyout;
            }

            public bool CanExecute( object parameter )
            {
                return _view.IsValid;
            }

            public void Execute( object parameter )
            {
                _flyout.IsOpen = false;
            }

            public event EventHandler CanExecuteChanged
            {
                add { CommandManager.RequerySuggested += value; }
                remove { CommandManager.RequerySuggested -= value; }
            }
        }

        #endregion Private Types

        #region Private Data

        private LauncherLogo _logo;
        private Metro.Flyout _activeFlyout;

        private double _actualWindowHeight;
        private double _actualWindowWidth;
        private double _baseWindowHeight; // Height of the window minus the tile container

        private FlyoutMode _flyoutsMode = FlyoutMode.Inwards;

        private bool _showDocumentationHelpAtStart = true;

        #endregion Private Data

        #region Public Properties

        public string UserName { get; private set; } = "Undefined";
        public Status ToolkitStatus { get; private set; } = new Status();
        public Status GameStatus { get; private set; } = new Status();

        public EnvironmentalVariableHelper EnvironmentVariableHelper { get; private set; } = new EnvironmentalVariableHelper();
        public LauncherWindowData.RunConfig RunConfig { get; private set; }
        public LauncherWindowData.EnvironmentVariables EnvironmentVariables { get; private set; }

        public double CalculatedWindowHeight => _baseWindowHeight + _tiles.ActualHeight - ( (!_logo.IsLarge)? _logo.Large.ParentMargin.Top - _logo.Small.ParentMargin.Top : 0 );

        // For some reason, Binding from LauncherWindow.xaml to a static property in Launcher.Settings causes the application to crash
        // But in this case, it doesn't matter too much because we can just put the setting here as it doesn't need to be read from anywhere else
        public FlyoutMode FlyoutsMode { get { return _flyoutsMode; } set { _flyoutsMode = value; OnPropertyChanged(); } }

        #endregion Public Properties

        #region C-Tor

        partial void InitializeAnalytics();

        public LauncherWindow()
        {
            InitializeComponent();
            _tiles.StringProvider.Providers.Add( _externalAppsView.Manager );

            _baseWindowHeight = Height - _tiles.RowHeight.Value * _tiles.Rows;

            ToolkitStatus.Change += () => { OnPropertyChanged( "ToolkitStatus" ); };
            GameStatus.Change += () => { OnPropertyChanged( "GameStatus" ); };

            GlowBrush = null;
            DataContext = this;

            // Grab the current user display name as an example
            // In future there should be a centralised method for all toolkit programs to be able to retrieve user info (rather than all doing it independently)
            UserName = ( (App)App.Current ).User.DisplayName;
            _logo = new LauncherLogo();

            _workspacesView.OnUserChanged += ( Workspaces.Database.User user ) => { _packagesView.User = user; };
            _settingsFlyout.CloseCommand = new CloseSettingsFlyout( _settingsView, _settingsFlyout );

            _settingsView._reloadDefaults.Click += ( object sender, RoutedEventArgs e ) =>
            {
                ReloadDefaultSettings();

                double targetHeight = CalculatedWindowHeight + ( ( _recentFilesFlyout.IsOpen ) ? _recentFilesFlyout.Height : 0 );
                ResizeWindowHeightTo( targetHeight );
            };

            Settings = new UserSettings();

            EnvironmentVariableHelper.Source = this;
            Closed += ( object sender, EventArgs e ) => { EnvironmentVariableHelper.Shutdown(); };
        }

        #endregion C-Tor

        #region RedMainWindow implementation

        protected override void WriteToSettings( RedSettings settings )
        {
            UserSettings userSettings = (UserSettings)settings;

            userSettings.PerforceServerAddress = Launcher.Settings.PerforceServerAddress;
            userSettings.PerforceServerPort = Launcher.Settings.PerforceServerPort;
            userSettings.DatabaseServer = Launcher.Settings.DatabaseServer;
            userSettings.DatabaseName = Launcher.Settings.DatabaseName;
            userSettings.EditorPackagesInstallationDirectory = Launcher.Settings.EditorPackagesInstallationDirectory;
            userSettings.ToolkitPackagesInstallationDirectory = Launcher.Settings.ToolkitPackagesInstallationDirectory;
            userSettings.EnginePackagesInstallationDirectory = Launcher.Settings.EnginePackagesInstallationDirectory;
            userSettings.WorkspaceInstallationDirectory = Launcher.Settings.WorkspaceInstallationDirectory;
            userSettings.DisplayRecentFilesByDefault = Launcher.Settings.DisplayRecentFilesByDefault;

            userSettings.LauncherWindowSettings.EnvironmentVariables.Create = Launcher.Settings.AddRuntimeToEnvironmentalVariables;
            userSettings.LauncherWindowSettings.EnvironmentVariables.Persist = Launcher.Settings.PersistEnvironmentalVariablesAfterShutdown;

            userSettings.LauncherWindowSettings.Flyouts.Direction = FlyoutsMode;
            userSettings.LauncherWindowSettings.Flyouts.RecentFilesHeight = _recentFilesFlyout.Height;
            userSettings.LauncherWindowSettings.ShowDocumentationHelpAtStartup = _showDocumentationHelpAtStart;

            _logo.WriteToSettings( userSettings );
            _packagesView.WriteToSettings( userSettings );
            _settingsView.WriteToSettings( userSettings );
            _workspacesView.WriteToSettings( userSettings );
            _externalAppsView.WriteToSettings();

            SaveTileLayout();
        }

        protected override async void LoadFromSettings( RedSettings settings )
        {
            UserSettings userSettings = (UserSettings)settings;

            // User scope
            Launcher.Settings.PerforceServerAddress = userSettings.PerforceServerAddress;
            Launcher.Settings.PerforceServerPort = userSettings.PerforceServerPort;
            Launcher.Settings.DatabaseServer = userSettings.DatabaseServer;
            Launcher.Settings.DatabaseName = userSettings.DatabaseName;
            Launcher.Settings.EditorPackagesInstallationDirectory = userSettings.EditorPackagesInstallationDirectory;
            Launcher.Settings.ToolkitPackagesInstallationDirectory = userSettings.ToolkitPackagesInstallationDirectory;
            Launcher.Settings.EnginePackagesInstallationDirectory = userSettings.EnginePackagesInstallationDirectory;
            Launcher.Settings.WorkspaceInstallationDirectory = userSettings.WorkspaceInstallationDirectory;
            Launcher.Settings.DisplayRecentFilesByDefault = userSettings.DisplayRecentFilesByDefault;

            Launcher.Settings.AddRuntimeToEnvironmentalVariables = userSettings.LauncherWindowSettings.EnvironmentVariables.Create;
            Launcher.Settings.PersistEnvironmentalVariablesAfterShutdown = userSettings.LauncherWindowSettings.EnvironmentVariables.Persist;

            FlyoutsMode = userSettings.LauncherWindowSettings.Flyouts.Direction;
            _recentFilesFlyout.Height = userSettings.LauncherWindowSettings.Flyouts.RecentFilesHeight;
            _showDocumentationHelpAtStart = userSettings.LauncherWindowSettings.ShowDocumentationHelpAtStartup;

            _logo.LoadFromSettings( userSettings );
            _packagesView.LoadFromSettings( userSettings );
            _settingsView.LoadFromSettings( userSettings );
            _workspacesView.LoadFromSettings( userSettings );
            _externalAppsView.LoadFromSettings();

            RunConfig = userSettings.LauncherConfig.RunConfig;
            EnvironmentVariables = userSettings.LauncherConfig.EnvironmentVariables;
            _tiles.MinVersion = userSettings.LauncherConfig.TileMinVersion;

            LoadTileLayout();

            if( _tiles.Contents.Count == 0 )
            {
                LoadDefaultTilePreset();
            }

            await _workspacesView.InitializeOnce();

            await CheckVersion();
            await EnvironmentVariableHelper.Refresh();
        }

        #endregion RedMainWindow implementation

        #region Protected Methods

        protected override void OnClosing( CancelEventArgs e )
        {
            if ( !_settingsView.IsValid )
            {
                e.Cancel = true;
            }
            else if( _workspacesView.BusyWorkspacesCount > 0 )
            {
                e.Cancel = true;
            }
            else if( _packagesView.IsAnyPackageInstalling )
            {
                e.Cancel = true;
            }
            else
            {
                base.OnClosing( e );
            }
        }

        #endregion Protected Methods

        #region Private Methods

        #region Tile Layout

        private void EnableRunTiles( bool enabled )
        {
            string[] funcs = { "toolkit", "game", "get_latest_install", "scripts" };

            foreach ( string func in funcs )
            {
                Controls.Tile.Tile tile = _tiles.FindTile( "main", func );
                tile.IsEnabled = enabled;
            }
        }

        private void LoadDefaultTilePreset()
        {
            string path = Path.Combine( AppDomain.CurrentDomain.BaseDirectory, "data", "tiles.presets" );

            if ( File.Exists( path ) )
            {
                XDocument doc = XDocument.Load( path );
                IEnumerable< XElement > presets = from preset in doc.Descendants( "preset" ) where preset.Attribute( "name" ).Value == "Basic" select preset;

                _tiles.FromXml( presets.First() );
            }
        }

        private void LoadTileLayout()
        {
            string path = Path.Combine( Launcher.Settings.ApplicationDataDirectoryPath, "tileLayout.xml" );

            if ( File.Exists( path ) )
            {
                XDocument doc = XDocument.Load( path );
                _tiles.FromXml( doc.Root );
            }
        }

        private void SaveTileLayout()
        {
            string path = Path.Combine( Launcher.Settings.ApplicationDataDirectoryPath, "tileLayout.xml" );

            XElement containerElement = _tiles.ToXml();

            XmlHelper.Save( containerElement, path );
        }

        #endregion Tile Layout

        #region Version

        private async Task CheckVersion()
        {
            // Don't check the version if a debugger is attached, most likely scenario is the app was manually compiled
            // which means the version number is the default "1.0.0.0"
            if ( Debugger.IsAttached || Array.Exists( Environment.GetCommandLineArgs(), arg => arg.Equals( "-no-version-check" ) ) )
            {
                return;
            }

            string sql = $@"SELECT TOP 1 lmv.*, bo.OUTPUT_DIR FROM [{Launcher.Settings.DatabaseName}].[dbo].[LauncherMinVersion] as lmv
                            INNER JOIN [buildsystem].[dbo].[BUILD_OUTPUT] as bo ON bo.BUILD_NUM = lmv.build_num
                            ORDER BY lmv.[created] desc";

            await DatabaseHelper.FetchAsyncSafe( sql, Launcher.Settings.DatabaseConnectionString, LatestVersionFetchCallback );
        }

        public void LatestVersionFetchCallback( Dictionary<string, object> row )
        {
            int buildId = (int)row[ "build_num" ];

            App app = (App)Application.Current;

            if ( app.BuildId < buildId )
            {
                if ( RedMessageBox.Show( this, RedDialogType.OkCancel, "Red Launcher will now close. Press OK to run the updater", "New version required", RedMessageBox.Mode.Info ) )
                {
                    FilePath pathToInstaller = (string)row[ "OUTPUT_DIR" ];

                    pathToInstaller = Path.Combine( pathToInstaller, "Launcher.Installer.msi" );

                    Process.Start( pathToInstaller );
                }

                Close();
            }
        }

        #endregion Version

        #region Run

        private P4.Mediator CreateMediator( Workspaces.Workspace workspace )
        {
            P4.Mediator mediator = new P4.Mediator( Launcher.Settings.PerforceServer, workspace.Credentials.Name, workspace.PerforceName );

            return mediator;
        }

        private async Task RunToolkit( Packages.Package toolkit, Packages.Package engine, Workspaces.Workspace workspace, string openFile = null )
        {
            EnableRunTiles( false );

            ToolkitStatus.Check( _settingsView.IsValid, Status.Source.Settings, "Invalid settings" );

            if ( _packagesView.ToolkitPackagesControl.ActivePackage != null )
            {
                await Run.Toolkit( _packagesView.PackageProcessManager, ToolkitStatus, toolkit, engine, workspace );
            }
            else
            {
                await Run.Exe( RunConfig.Editor, _packagesView.PackageProcessManager, ToolkitStatus, toolkit, engine, workspace, openFile );
            }

            EnableRunTiles( true );
        }

        private async void RunGame( Packages.Package pkg, Workspaces.Workspace workspace )
        {
            EnableRunTiles( false );

            GameStatus.Check( _settingsView.IsValid, Status.Source.Settings, "Invalid settings" );

            await Run.Exe( RunConfig.Game, _packagesView.PackageProcessManager, GameStatus, pkg, null, workspace );

            EnableRunTiles( true );
        }

        private async void RunScriptEditor( Packages.Package pkg, Workspaces.Workspace workspace )
        {
            EnableRunTiles( false );

            GameStatus.Check( _settingsView.IsValid, Status.Source.Settings, "Invalid settings" );

            await Run.Exe( RunConfig.ScriptEditor, _packagesView.PackageProcessManager, GameStatus, pkg, null, workspace );

            EnableRunTiles( true );
        }

        private async Task<bool> InstallMostRecent()
        {
            await _packagesView.PerformFirstTimeFetch();

            // We only do one install check with EditorPackagesControl because it will internally check the engine packages
            // And we'd get a second processes window if we did it ourselves
            if ( await _packagesView.EditorPackagesControl.InstallCheckProcesses( _packagesView.EditorPackagesControl.Manager.MostRecent ) )
            {
                Task<bool>[] installTasks = new Task<bool>[]
                {
                _packagesView.EditorPackagesControl.Manager.InstallLatest(),
                _packagesView.EnginePackagesControl.Manager.InstallLatest()
                };

                IEnumerable<bool> results = await Task.WhenAll( installTasks );

                return results.All( result => result );
            }

            return false;
        }

        private async void GetLatestAndInstall()
        {
            EnableRunTiles( false );

            ToolkitStatus.Clear();

            ToolkitStatus.Check( _settingsView.IsValid, Status.Source.Settings, "Invalid settings" );
            ToolkitStatus.Check( _workspacesView.ActiveWorkspace != null, Status.Source.WorkspaceManager, "No Workspace selected" );

            if ( ToolkitStatus.HasErrorOnSource( Status.Source.WorkspaceManager ) )
            {
                // Open the workspaces flyout and prompt the user to select a workspace
                if( !_workspacesFlyout.IsOpen )
                {
                    ToggleFlyout( _workspacesFlyout );
                }

                if ( _workspacesFlyout.IsOpen )
                {
                    // Wait for the flyout to animate before displaying the help adorner
                    // This is because we want it to appear the flyout animates to, not where it started
                    await Task.Delay( 750 );

                    _workspacesView.Help = "Please select the workspace for which you wish to get latest.\n\nIf you do not select a workspace the operation will be cancelled\n\nIf you do not have any workspaces available, you will need to speak to IT or your team lead";
                    _workspacesView.IsHelpOpen = true;

                    TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();

                    RoutedEventHandler handler = ( object sender, RoutedEventArgs e ) => { if ( !_workspacesFlyout.IsOpen ) tcs.SetResult( true ); };
                    _workspacesFlyout.IsOpenChanged += handler;

                    await tcs.Task;
                    _workspacesView.IsHelpOpen = false;
                    _workspacesFlyout.IsOpenChanged -= handler;

                    // Recheck to make sure user selected a workspace
                    ToolkitStatus.Check( _workspacesView.ActiveWorkspace != null, Status.Source.WorkspaceManager, "No Workspace selected" );
                }
            }

            if ( !ToolkitStatus.HasError )
            {
                await _workspacesView.GetLatest( _workspacesView.ActiveWorkspace, false );
                await InstallMostRecent();
            }

            EnableRunTiles( true );
        }

        private async Task ShowProcessDialog( string exePath )
        {
            if ( exePath == null )
                return;

            string processName = Path.GetFileNameWithoutExtension( exePath );
            Diagnostics.Process.Monitor monitor = _packagesView.PackageProcessManager[ processName ];

            await monitor.Refresh();

            if ( monitor.Count > 0 )
            {
                string message = $"The following applications are preventing you from running a new instance of '{exePath}'";
                WaitForProcessShutdownDialog dialog = new WaitForProcessShutdownDialog() { Owner = this, Message = message };
                dialog.Monitors.Add( monitor );
                dialog.ShowDialog();
            }
        }

        #endregion Run

        #region Flyouts

        /// <summary>
        /// Animates the height of the window using the same style of animation as metro flyouts
        /// This way the flyout will appear to enlarge the window rather than overlap
        /// </summary>
        private void ResizeWindowHeightTo( double height )
        {
            DoubleAnimationUsingKeyFrames heightAnimation = new DoubleAnimationUsingKeyFrames();
            SplineDoubleKeyFrame frame = new SplineDoubleKeyFrame( height, KeyTime.FromTimeSpan( TimeSpan.FromSeconds( 0.75 ) ), new KeySpline( 0.25, 1, 0.05, 1 ) );
            heightAnimation.KeyFrames.Add( frame );

            ( (IAnimationWrapper)this ).BeginAnimation( HeightProperty, heightAnimation );
        }

        private void ToggleFlyout( Metro.Flyout flyout )
        {
            if ( _activeFlyout != null && _activeFlyout.CloseCommand != null && !_activeFlyout.CloseCommand.CanExecute( null ) )
            {
                return;
            }

            if ( _activeFlyout != null && _activeFlyout != flyout )
            {
                _activeFlyout.IsOpen = false;
            }

            flyout.IsOpen = !flyout.IsOpen;

            if ( flyout.IsOpen )
            {
                _activeFlyout = flyout;
            }
            else
            {
                _activeFlyout = null;
            }
        }

        private void WatchForDatabaseSettingChanges()
        {
            bool databaseChanged = false;

            Launcher.Settings.StaticPropertyChanged += ( object sender, PropertyChangedEventArgs e ) =>
            {
                if ( e.PropertyName == "DatabaseName" )
                {
                    databaseChanged = true;
                }
            };

            _settingsFlyout.IsOpenChanged += async ( object sender, RoutedEventArgs e ) =>
            {
                if ( !_settingsFlyout.IsOpen )
                {
                    if ( databaseChanged )
                    {
                        databaseChanged = false;
                        await _workspacesView.Reinitialize();
                    }
                }
            };
        }

        /// <summary>
        /// Prevent the Recent Files flyout from sliding behind the side flyouts
        /// </summary>
        /// <remarks>
        /// Metro internally overrides the HorizontalAlignment to "Stretch" for Top/Bottom aligned flyouts, which means that
        /// if you have 2 flyouts (one horizonal, one vertical) open at once they will overlap regardless of size.
        /// 
        /// This function will prevent that from happening by applying an animation to that property, which will prevent any
        /// new values from being set outside of the animation system.
        /// </remarks>
        private void LockRecentFilesToHorizonalAlignmentLeft()
        {
            DiscreteObjectKeyFrame kf = new DiscreteObjectKeyFrame( HorizontalAlignment.Left, TimeSpan.FromSeconds( 0.001 ) );
            ObjectAnimationUsingKeyFrames anim = new ObjectAnimationUsingKeyFrames();
            anim.KeyFrames.Add( kf );
            _recentFilesFlyout.BeginAnimation( HorizontalAlignmentProperty, anim );
        }

        #endregion Flyouts

        #region Logo

        private void AdjustToLogoSizeChange( Thickness tileMargin, double newWindowHeight )
        {
            ThicknessAnimation anim = new ThicknessAnimation( tileMargin, TimeSpan.FromSeconds( 0.5 ) );
            DoubleAnimation heightAnimation = new DoubleAnimation( newWindowHeight, TimeSpan.FromSeconds( 0.5 ) );

            anim.EasingFunction = new BackEase();
            heightAnimation.EasingFunction = anim.EasingFunction;

            _tiles.BeginAnimation( MarginProperty, anim );

            ( (IAnimationWrapper)this ).BeginAnimation( HeightProperty, heightAnimation );
        }

        #endregion Logo

        #endregion Private Methods

        #region IAnimationWrapper

        void IAnimationWrapper.BeginAnimation( DependencyProperty dp, AnimationTimeline anim )
        {
            if ( dp == HeightProperty )
            {
                _actualWindowHeight = (double)this.ExtractTargetValue( anim );
            }
            else if ( dp == WidthProperty )
            {
                _actualWindowWidth = (double)this.ExtractTargetValue( anim );
            }

            BeginAnimation( dp, anim );
        }

        public object GetActualValue( DependencyProperty dp )
        {
            if ( dp == HeightProperty )
            {
                return _actualWindowHeight;
            }
            else if ( dp == WidthProperty )
            {
                return _actualWindowWidth;
            }

            return GetValue( dp );
        }

        public void SetValueDirect( DependencyProperty dp, object value )
        {
            if ( dp == HeightProperty )
            {
                BeginAnimation( dp, null );
                _actualWindowHeight = (double)value;
                Height = _actualWindowHeight;
            }
        }

        #endregion IAnimationWrapper

        #region Event Handlers

        private void LauncherWindow_Loaded( object loaded_sender, RoutedEventArgs loaded_args )
        {
            _actualWindowHeight = Height;
            _actualWindowWidth = Width;

            if ( _logo.IsLarge )
            {
                _tiles.Margin = _logo.Large.ParentMargin;
            }
            else
            {
                _tiles.Margin = _logo.Small.ParentMargin;
            }

            // Attach the logo to the launcher window
            _logo.Owner = this;

            // Now that it's attached, make sure it's positioned correctly relative to us
            _logo.UpdatePosition();

            _logo.ShowActivated = false;

            _logo.Shrinking += _logo_Shrinking;
            _logo.Growing += _logo_Growing;

            // Finally, make it visible to the user!
            _logo.Show();

            _packagesView.EditorPackagesControl.ActivePackageChanged += ( object sender, EventArgs e ) => { ToolkitStatus.Clear( Status.Source.Toolkit ); };
            _packagesView.ToolkitPackagesControl.ActivePackageChanged += ( object sender, EventArgs e ) => { ToolkitStatus.Clear( Status.Source.Toolkit ); };
            _packagesView.EnginePackagesControl.ActivePackageChanged += ( object sender, EventArgs e ) => { ToolkitStatus.Clear( Status.Source.Engine ); GameStatus.Clear( Status.Source.Engine ); };

            _workspacesView.OnActiveWorkspaceChanged += () => { ToolkitStatus.Clear( Status.Source.WorkspaceManager ); GameStatus.Clear( Status.Source.WorkspaceManager ); };

            Launcher.Settings.StaticPropertyChanged += Settings_PropertyChanged;

            // Now that the tiles have potentially been loaded from xml (and the rowcount is different), we need to recalculate the height of the main window
            SetValueDirect( HeightProperty, CalculatedWindowHeight );

            _tiles.UserPresetsLocation = Path.Combine( Launcher.Settings.ApplicationDataDirectoryPath, "user.presets" );
            _tiles.HeightChanged += _tiles_HeightChanged;
            _tiles.RegisterClickCallback( "main", Tile_Click );
            _tiles.RegisterDoubleClickCallback( "main", TileDouble_Click );
            _tiles.RegisterClickCallback( "application", ( path ) => { _externalAppsView.Manager.FindAndRunApplicationWithPath( path ); } );
            _externalAppsView.DragDrop.RegisterTarget( _tiles );

            _recentFilesFlyout.IsOpen = Launcher.Settings.DisplayRecentFilesByDefault;
            _documentationHelp.IsOpen = _showDocumentationHelpAtStart;

            LockRecentFilesToHorizonalAlignmentLeft();

            WatchForDatabaseSettingChanges();

            InitializeAnalytics();
        }

        private void _logo_Growing()
        {
            double targetWindowHeight = _actualWindowHeight + ( _logo.Large.ParentMargin.Top - _logo.Small.ParentMargin.Top );
            AdjustToLogoSizeChange( _logo.Large.ParentMargin, targetWindowHeight );
        }

        private void _logo_Shrinking()
        {
            double targetWindowHeight = _actualWindowHeight - ( _logo.Large.ParentMargin.Top - _logo.Small.ParentMargin.Top );
            AdjustToLogoSizeChange( _logo.Small.ParentMargin, targetWindowHeight );
        }

        private void Tile_Click( string function )
        {
            switch ( function )
            {
                case "toolkit":
                    RunToolkit( _packagesView.ActiveToolkitOrEditorManager.ActivePackage, _packagesView.EnginePackagesControl.ActivePackage, _workspacesView.ActiveWorkspace ).DoNotAwait();
                    break;

                case "get_latest_install":
                    GetLatestAndInstall();
                    break;

                case "game":
                    RunGame( _packagesView.EnginePackagesControl.ActivePackage, _workspacesView.ActiveWorkspace );
                    break;

                case "scripts":
                    RunScriptEditor( _packagesView.EnginePackagesControl.ActivePackage, _workspacesView.ActiveWorkspace );
                    break;

                case "packages":
                    if ( !_packagesFlyout.IsOpen )
                    {
                        _packagesView.PerformFirstTimeFetch().DoNotAwait();

                    }
                    ToggleFlyout( _packagesFlyout );
                    break;

                case "workspaces":
                    ToggleFlyout( _workspacesFlyout );
                    break;

                case "targets":
                    ToggleFlyout( _targetsFlyout );
                    break;

                case "apps":
                    ToggleFlyout( _externalAppsFlyout );
                    break;

                case "recent":
                    // Don't use ToggleFlyout() as we want this flyout to co-exist with other flyouts
                    _recentFilesFlyout.IsOpen = !_recentFilesFlyout.IsOpen;
                    break;

                case "settings":
                    ToggleFlyout( _settingsFlyout );
                    break;
            }
        }

        private async void TileDouble_Click( string function )
        {
            switch ( function )
            {
                case "toolkit":
                {
                    string exe = _packagesView.ActiveToolkitOrEditorManager.ActivePackage?.ExecutablePath;
                    await ShowProcessDialog( exe );
                    break;
                }

                case "game":
                {
                    string exe = _packagesView.EnginePackagesControl.ActivePackage?.ExecutablePath;
                    await ShowProcessDialog( exe );
                    break;
                }

                case "scripts":
                {
                    string exe = RunConfig.ScriptEditor.ExeOverride;
                    await ShowProcessDialog( exe );
                    break;
                }
            }
        }

        private void _tiles_HeightChanged( int difference, double sizeDifference )
        {
            double newSize = _actualWindowHeight + sizeDifference;
            Debug.Assert( sizeDifference == difference * _tiles.RowHeight.Value );

            ResizeWindowHeightTo( newSize );

            Debug.Print( $"Window height changed from {_actualWindowHeight}/{Height} to {newSize} (Rows added: {difference}, Height added: {sizeDifference}" );
        }

        private void Settings_PropertyChanged( object sender, PropertyChangedEventArgs e )
        {
            if ( _settingsView.IsValid )
            {
                ToolkitStatus.Clear( Status.Source.Settings );
                GameStatus.Clear( Status.Source.Settings );
            }
        }

        /// <summary>
        /// Ensures that when a user clicks on a flyout it is brought to the front of other flyouts that may be open
        /// </summary>
        private void Flyout_MouseDown( object sender, MouseButtonEventArgs e )
        {
            if ( _activeFlyout != null )
            {
                Panel.SetZIndex( _activeFlyout, 0 );
            }

            Panel.SetZIndex( _recentFilesFlyout, 0 );

            Metro.Flyout flyout = (Metro.Flyout)sender;
            Panel.SetZIndex( flyout, 1 );
        }

        private void RecentFiles_RecentFileActivated( string file )
        {
            RunToolkit( _packagesView.ActiveToolkitOrEditorManager.ActivePackage, _packagesView.EnginePackagesControl.ActivePackage, _workspacesView.ActiveWorkspace, file ).DoNotAwait();
        }

        private void HelpCommand_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            //TODO: open different help pages depending on which tabs are open
            Process.Start( @"http://confluence:8090/display/TL/RED+Launcher" );
            _showDocumentationHelpAtStart = false;
        }

        private void _packagesStatus_Click( object sender, RoutedEventArgs e )
        {
            if ( _packagesStatus.Opacity > 0 )
            {
                ToggleFlyout( _packagesFlyout );
            }
        }

        private void _workspacesStatus_Click( object sender, RoutedEventArgs e )
        {
            if ( _workspacesStatus.Opacity > 0 )
            {
                ToggleFlyout( _workspacesFlyout );
            }
        }

        #endregion Event Handlers

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged( [CallerMemberName] string propertyName = null )
        {
            PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
        }

        #endregion INotifyPropertyChanged

        #region IEnvironmentalVariableSource

        bool IEnvironmentalVariableSource.HasConfig => EnvironmentVariables != null;
        bool IEnvironmentalVariableSource.HasValues => true;

        void IEnvironmentalVariableSource.SetEnvironmentVariables()
        {
            Environment.SetEnvironmentVariable( EnvironmentVariables.Path, System.Reflection.Assembly.GetExecutingAssembly().Location, EnvironmentVariableTarget.User );
        }

        void IEnvironmentalVariableSource.ClearEnvironmentVariables()
        {
            Environment.SetEnvironmentVariable( EnvironmentVariables.Path, null, EnvironmentVariableTarget.User );
        }

        #endregion IEnvironmentalVariableSource
    }
}
